#include "degfx/font.h"

#define MXMODULE_LOADPCX
#define MXMODULE_BITPIXEL

#include <assert.h>
#include <string.h>

static void mx__font_destroy(void *atom)
{
	MX_FONT *font = (MX_FONT *) atom;

	assert(font);

	if (font->_index)
		mx_free(font->_index);

	if (font->_data)
		mx_free(font->_data);
}

MX_FONT *mx_font(const int height)
{
	MX_FONT *font = (MX_FONT *) mx_atom((MX_FONT *) 0, mx__font_destroy,
										sizeof(MX_FONT));

	if (!font)
		return font;

	assert(font);
	MXDEBUG_ATOMNAME(font, "font");

	font->_height = height;
	font->_drawchar = mx__font_bytechar;

	font->_index = 0;
	font->_sections = 0;
	font->_data = 0;
	font->_datalen = 0;

	return font;
}

static int mx__loadfont_sortindex(const void *d1, const void *d2)
{
	const MX__FONT_INDEX *index1 = (MX__FONT_INDEX *) d1;
	const MX__FONT_INDEX *index2 = (MX__FONT_INDEX *) d2;

	return (index1->_start < index2->_start) ? -1 : 1;
}

void mx_font_add(MX_FONT * font, long c, int width, const unsigned char *data, unsigned int datalen)
{
	unsigned int bytes;
	MX__FONT_INDEX *range;

	assert(font);

	range = mx__font_range(font, c);
	if (!range) {
		bytes = (font->_sections + 1) * sizeof(MX__FONT_INDEX);
		if (!font->_index)
			font->_index = (MX__FONT_INDEX *) mx_malloc(bytes);
		else
			font->_index = (MX__FONT_INDEX *) mx_realloc(font->_index, bytes);

		memset(&font->_index[font->_sections], 0, sizeof(MX__FONT_INDEX));

		font->_index[font->_sections]._start = c & (~0x7f);

		++font->_sections;
		qsort(font->_index, font->_sections, sizeof(MX__FONT_INDEX), mx__loadfont_sortindex);

		range = mx__font_range(font, c);
	}

	assert(range);

	range->_width[c - range->_start] = width;
	range->_index[c - range->_start] = font->_datalen;

	bytes = font->_datalen + datalen;
	if (!font->_data)
		font->_data = (unsigned char *) mx_malloc(bytes);
	else
		font->_data = (unsigned char *) mx_realloc(font->_data, bytes);

	memcpy(&font->_data[font->_datalen], data, datalen);
	font->_datalen += datalen;
}

static void mx__font_load_find_char(const MX_BITMAP * bmp, int *x, int *y, int *w, int *h, int ystep, MX_PIXEL c2)
{
	/* look for top left corner of character */
	while (1) {
		if ((mx_bitmap_getpixel(bmp, *x, *y) != c2)
			&& (mx_bitmap_getpixel(bmp, *x - 1, *y) == c2)
			&& (mx_bitmap_getpixel(bmp, *x, *y - 1) == c2)
			&& (mx_bitmap_getpixel(bmp, *x - 1, *y - 1) == c2))
			break;

		(*x)++;

		if (*x >= mx_w(bmp)) {
			*x = 0;
			*y += ystep;
			ystep = 1;
			if (*y >= mx_h(bmp)) {
				*w = 0;
				*h = 0;
				return;
			}
		}
	}

	/* look for right edge of character */
	*w = 1;
	while ((mx_bitmap_getpixel(bmp, *x + *w, *y) != c2)
		   && (mx_bitmap_getpixel(bmp, *x + *w, *y - 1) == c2))
		(*w)++;

	/* look for bottom edge of character */
	*h = 1;
	while ((mx_bitmap_getpixel(bmp, *x, *y + *h) != c2)
		   && (mx_bitmap_getpixel(bmp, *x - 1, *y + *h) == c2))
		(*h)++;
}

/**font !Load a font from a bitmap. */
MX_FONT *mx_font_bitmap(const MX_BITMAP * bitmap, int start)
{
	MX_FONT *font = 0;

	MX_VECTOR(unsigned char) cdata;
	int x = 0, y = 0, w, h;
	int maxh = 1;
	MX_PIXEL c = mx_bitmap_getpixel(bitmap, 0, 0);

	assert(bitmap);

	mx_vector(&cdata);

	for (;;) {
		int bx, by;
		unsigned int len = 0;

		mx__font_load_find_char(bitmap, &x, &y, &w, &h, maxh, c);
		maxh = MX_MAX(maxh, h);

		if ((w <= 0) || (h <= 0))
			break;

		mx_vector_reserve(&cdata, w * maxh + 1);

		for (by = y; by < y + maxh; by++) {
			for (bx = x; bx < x + w; bx++) {
				const MX_PIXEL p = mx_bitmap_getpixel(bitmap, bx, by);

				cdata.data[len] = (MXR(p) + MXG(p) + MXB(p)) / 3;
				++len;
			}
		}

		if (!font)
			font = mx_font(maxh);
		mx_font_add(font, start++, w, cdata.data, len);

		x += w;
	}

	font->_height = maxh;

	mx_vector_free(&cdata);

	return font;
}

/**font !Load a font from a PCX file. */
MX_FONT *mx_font_pcx(const char *filename, int start)
{
	MX_FONT *font;
	MX_BITMAP *bitmap = mx_bitmap_pcx_greyscale(filename);

	if (!bitmap)
		return 0;

	font = mx_font_bitmap(bitmap, start);

	mx_delete(bitmap);

	return font;
}
